/**
 * JAVACC DEMO 1.0
 */
package com.apache.uct.common.filter;

import com.apache.jwt.PostHttps;
import com.apache.passport.common.PassportHelper;
import com.apache.passport.common.XmlWhiteUtils;
import com.apache.tools.ConfigUtil;
import com.apache.tools.StrUtil;
import com.apache.uct.common.ToolsUtil;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.io.IOUtils;
import org.apache.http.NameValuePair;
import org.apache.http.message.BasicNameValuePair;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.*;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.Socket;
import java.net.URLEncoder;
import java.util.*;

/**
 * description:  单点登录客户端
 *
 * @author Hou Dayu 创建时间：2016-12-25
 */
public class SsoClientFilter implements Filter {

    private static final Logger log = LoggerFactory.getLogger(SsoClientFilter.class);

    private static final String SUFFIX = "js,css,png,jpg,gif,bmp,swf,fla,ico";

    //自定义登录页面
    private String login_url = "";

    //cookieName 名称
    private String cookieName = "";

    //获取系统名称
    private String sysEname = "";

    //是否使用统一登录系统登录 T/F
    private String login_pass = "";

    private String reqUrl = "";

    //http,socket,https,webservice
    private String reqType = "";

    private String checkIp = "";

    private String checkPort = "";

    private String whiteUrl = "";

    private String isClearSession = "";

    private String sysCode = "";

    // 白名单列表
    private Map<String, ArrayList<String>> whiteMap = new HashMap<String, ArrayList<String>>();

    /**
     * TODO 简单描述该方法的实现功能（可选）.
     *
     * @see javax.servlet.Filter#destroy()
     */
    public void destroy() {

    }

    /**
     * TODO 简单描述该方法的实现功能（可选）.
     *
     * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)
     */
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;
        resp.setCharacterEncoding("UTF-8");
        resp.setDateHeader("expires", 0);
        resp.setHeader("Cache-Control", "no-cache");
        resp.setHeader("pragma", "no-cache");
        req.setCharacterEncoding("UTF-8");
        //CookeHeader(resp, req);
        String path_ = req.getContextPath();
        String basePath_ =
                req.getScheme() + "://" + req.getServerName() + ":" + req.getServerPort() + path_
                        + "/";
        //设置退出路径  页面中直接${outUrl}即可
        req.getSession().setAttribute("outUrl", basePath_);

        //获取uri信息
        String uri = req.getRequestURI();
        //获取后缀名
        String suffix = PassportHelper.getInstance().parseUrlSuffix(uri);
        //后缀名小写
        suffix = suffix.toLowerCase();
        //如果存在后缀为图片,css等格式,直接跳过,不拦截
        if (StrUtil.isNotNull(suffix)) {
            if (SUFFIX.contains(suffix)) {
                chain.doFilter(req, resp);
                return;
            }
        }
        //初始化白名单
        XmlWhiteUtils.getInstance().deWhiteXml(whiteMap, sysCode);
        //获取token
        String tokenCookie = PassportHelper.getInstance().getTokenId(req);

        //获取访问路径
        String path = req.getContextPath();
        //要跳转到哪个url
        String gotoURL = request.getParameter("go");

        String csetUrl = request.getScheme() + "://" + request.getServerName() + ":" + request
                .getServerPort() + path;
        if (gotoURL == null) {
            gotoURL = req.getRequestURL().toString();
        }
        //2015年2月2日09:16:52  增加sha1加密
        ArrayList<String> lst = new ArrayList<String>();

        //2015年6月8日15:55:16  获取请求中携带的参数
        String reqParams = "";
        if ("T".equals(ConfigUtil.getInstance().getValueByKey("is_url_params"))) {
            reqParams = PassportHelper.getInstance().getReqParams(req, "go");
            reqParams = PassportHelper.getInstance().isNov(reqParams, reqParams, "?" + reqParams);
        }

        if (ConfigUtil.getInstance().checkFileUpdate("")) {
            initValue();
        }

        //2015年2月4日11:07:24 更新 如果客户端根据token访问获取
        String pkt = req.getParameter("tokenId");
        if (StrUtil.isNull(tokenCookie) && !StrUtil.isNull(pkt)) {
            StringBuffer tokenUrl = new StringBuffer();
            lst.add(login_url);
            lst.add(pkt);
            lst.add(gotoURL + reqParams);
            //lst.add(gotoURL + "?" + reqParams);
            lst.add("apache");
            lst.add(csetUrl);
            Collections.sort(lst);
            String sha1Rtn = PassportHelper.getInstance().SHA1(lst);

            if ("T".equals(login_pass)) {
                //如果cookie为空,则跳转到登录页面
                tokenUrl.append(login_url);
                tokenUrl.append("?");
                tokenUrl.append("ptlang=" + sha1Rtn);
                tokenUrl.append("&");
                tokenUrl.append("tokenId=" + pkt);//设置远程cookie
                tokenUrl.append("&");
                //2015年6月8日15:56:39 添加url
                //tokenUrl.append("go=" + gotoURL + "?" + reqParams);//需要跳转的页面
                tokenUrl.append("go=" + gotoURL + reqParams);
                tokenUrl.append("&");
                tokenUrl.append("cset=" + csetUrl);

            } else {
                tokenUrl.append(login_url);
            }
            req.removeAttribute("tokenId");
            resp.sendRedirect(tokenUrl.toString());
            return;
        }/////客户端调用结束

        //统一登录系统平台回调参数
        String cset = csetUrl + "/cset";
        //2015年2月2日09:16:52  增加sha1加密
        lst.add(login_url);
        lst.add(sysEname);
        lst.add(cset);
        //2015年6月8日16:26:16 update
        //lst.add(gotoURL + "?" + reqParams);
        if (req.getRequestURI().equals(path + "/logout")) {
            lst.add(gotoURL);
        } else {
            lst.add(gotoURL + reqParams);
        }
        lst.add("apache");
        Collections.sort(lst);

        String sha1Rtn = PassportHelper.getInstance().SHA1(lst);

        StringBuffer loginUrl = new StringBuffer();
        if ("T".equals(login_pass)) {
            //如果cookie为空,则跳转到登录页面
            loginUrl.append(login_url);
            loginUrl.append("?");
            loginUrl.append("_client=" + sha1Rtn);
            loginUrl.append("&");
            loginUrl.append("sys=" + sysEname);//系统名称
            loginUrl.append("&");
            loginUrl.append("cset=" + URLEncoder.encode(cset, "UTF-8"));//设置远程cookie
            loginUrl.append("&");
            //2015年6月8日15:55:52 add
            //loginUrl.append("go=" + URLEncoder.encode(gotoURL, "UTF-8") + "?" + reqParams);//需要跳转的页面
            loginUrl.append("go=" + URLEncoder.encode(gotoURL, "UTF-8") + reqParams);
        } else {
            loginUrl.append(login_url);
        }

        //退出操作
        if (req.getRequestURI().equals(path + "/logout")) {
            doLogout(req, resp, chain, tokenCookie, loginUrl.toString());
        } else if (req.getRequestURI().equals(path + "/cset")) {
            setCookie(req, resp);//设置cookie
        } else if (!StrUtil.isNull(tokenCookie)) {
            authCookie(req, resp, chain, tokenCookie, loginUrl.toString());//校验cookie
        } else {
            boolean mark = whitePathFiter(uri, req);//白名单处理
            if (mark) {
                chain.doFilter(req, resp);
                return;
            } else {
                resp.sendRedirect(loginUrl.toString());
            }
        }
    }

    private boolean whitePathFiter(String uri, HttpServletRequest req) {

        String path = req.getServletPath();
        boolean mark = false;
        String whiteUrl = this.whiteUrl;
        /**白名单处理*/
        if (StrUtil.isNotNull(whiteUrl)) {
            String[] wus = whiteUrl.split(",");
            for (int i = 0; i < wus.length; i++) {
                String wurl = wus[i];
                if (StrUtil.isNotNull(wurl)) {
                    if (path.startsWith(wurl)) {
                        return true;
                    }
                }
            }
        }
        ArrayList<String> whiteUrl2 = whiteMap.get("whiteUrl");
        ArrayList<String> whiteParadigm = whiteMap.get("whiteParadigm");
        String ctx = req.getContextPath(); //获取上下文，如/项目名
        uri = uri.substring(ctx.length());
        //url白名单
        if (whiteUrl2.contains(uri)) {
            return true;
        } else {
            int wp = whiteParadigm.size();
            if (uri.length() > 1) {//主要是/造成,如guo/后面有内容,则进入以下方法
                for (int i = 0; i < wp; i++) {
                    if ((whiteParadigm.get(i)).contains("*")) {
                        String wdir = (whiteParadigm.get(i)).replace("*", "");
                        int s = uri.indexOf(wdir);
                        if (s == 0) {
                            return true;
                        }
                    } else {
                        if (!"".equals(whiteParadigm.get(i))) {
                            int s = uri.indexOf(whiteParadigm.get(i));
                            if (s == 0) {
                                return true;
                            }
                        }
                    }
                }
            }
        }
        return mark;
    }

    /**
     * description:  用户退出操作
     *
     * @param req
     * @param resp
     * @param chain
     * @param tokenCookie
     * @param loginUrl
     */
    private void doLogout(HttpServletRequest req, HttpServletResponse resp, FilterChain chain,
            String tokenCookie, String loginUrl) {
        try {
            String userEname = PassportHelper.getInstance().getCurrCookie(req, "_uc.sso");
            if (StrUtil.isNull(userEname)) {
                userEname = (String) req.getSession().getAttribute("message");
            }
            //2015年2月3日09:31:48 update 退出时根据配置文件内容,自动判断操作协议,进行操作,原只支持socket
            String str = "";

            if ("socket".equals(reqType)) {//ok
                str = socketSend(tokenCookie, req.getLocalAddr(), "logout", userEname);
            } else if ("http".equals(reqType)) {//ok
                str = httpSend(tokenCookie, req.getLocalAddr(), "logout", userEname);
            } else if ("https".equals(reqType)) {
                str = httpsSend(tokenCookie, req.getLocalAddr(), "logout", userEname);
            }

            if (!StrUtil.isNull(str)) {
                JSONObject result = new JSONObject(str);
                if (result.getBoolean("result")) {
                    String entity = result.getString("entity");
                    if ("T".equals(entity)) {
                        clearCookie(req, resp, "/");
                        resp.sendRedirect(loginUrl);
                    }
                    if ("N".equals(entity)) {
                        clearCookie(req, resp, "/");
                        resp.sendRedirect(loginUrl);
                    }

                } else {
                    resp.sendRedirect(loginUrl);
                    log.info("passport msg:[{}]", "与统一登录系统通讯失败,操作[证书认证]失败");
                }
            } else {
                log.info("passport msg:[{}]", "与统一登录系统通讯失败");
            }
        } catch (JSONException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * description:  证书校验,通过socket,http,https,webservice
     *
     * @param req
     * @param resp
     * @param chain
     * @param tokenCookie
     * @param loginUrl
     */
    private void authCookie(HttpServletRequest req, HttpServletResponse resp, FilterChain chain,
            String tokenCookie, String loginUrl) {
        try {
            String jsonStr = "";
            if ("socket".equals(reqType)) {//ok
                jsonStr = socketSend(tokenCookie, req.getLocalAddr(), "checkToken", "");
            } else if ("http".equals(reqType)) {//ok
                jsonStr = httpSend(tokenCookie, req.getLocalAddr(), "checkToken", "");
            } else if ("https".equals(reqType)) {
                jsonStr = httpsSend(tokenCookie, req.getLocalAddr(), "checkToken", "");
            }

            if (!StrUtil.isNull(jsonStr)) {
                JSONObject result = new JSONObject(jsonStr);
                if (result.getBoolean("result")) {
                    String entity = result.getString("entity");
                    String message = result.getString("message");
                    req.getSession().setAttribute("message", message);
                    if (tokenCookie.equalsIgnoreCase(entity)) {
                        Cookie _uc = new Cookie("_uc.sso", message);
                        _uc.setPath("/");
                        _uc.setMaxAge(-1);
                        resp.addCookie(_uc);
                        chain.doFilter(req, resp);
                    } else if ("O".equalsIgnoreCase(entity)) {
                        clearCookie(req, resp, "/");
                        log.info("passport msg:[{}]", "/common/403.jsp跳转,请检查是否存在此页面");
                        req.getRequestDispatcher("/common/403.jsp").forward(req, resp);
                    } else {
                        clearCookie(req, resp, "/");
                        resp.sendRedirect(loginUrl);
                    }
                } else {
                    resp.sendRedirect(loginUrl);
                    log.info("passport msg:[{}]", "与统一登录系统通讯失败,操作[证书认证]失败");
                }
            } else {
                log.info("passport msg:[{}]", "与统一登录系统通讯失败");
            }

        } catch (JSONException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ServletException e) {
            e.printStackTrace();
        }
    }

    /**
     * description:  设置本地cookie
     *
     * @param req
     * @param resp
     */
    private void setCookie(HttpServletRequest req, HttpServletResponse resp) {
        String token = req.getParameter("ticket");
        String ucsso = req.getParameter("ucsso");
        String gotoURL = req.getParameter("go");

        Cookie ticket = new Cookie(cookieName, token);
        ticket.setPath("/");
        ticket.setMaxAge(-1);
        Cookie ucssoCKey = new Cookie("_uc.sso", ucsso);
        ucssoCKey.setPath("/");
        ucssoCKey.setMaxAge(-1);
        try {
            resp.addCookie(ticket);
            resp.addCookie(ucssoCKey);
            if (StrUtil.isNotNull(gotoURL)) {
                gotoURL = gotoURL.replaceAll("\\|", "&");
                if (gotoURL.indexOf("?tokenId=") != -1) {
                    gotoURL = gotoURL.replaceAll("tokenId=" + token + "&", "");
                } else {
                    gotoURL = gotoURL.replaceAll("&tokenId=" + token, "");
                }
                resp.sendRedirect(gotoURL);
                return;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    /**
     * description:  http发送数据信息
     *
     * @param tokenCookie
     * @param type
     * @param userEname
     * @return
     */
    private String httpSend(String tokenCookie, String ip, String type, String userEname) {
        String rtn = "";
        rtn = checkTokenGet(tokenCookie, ip, type, userEname);
        return rtn;
    }

    /**
     * description:  https发送信息
     *
     * @param tokenCookie
     * @param ip
     * @param type
     * @param userEname
     * @return
     */
    private String httpsSend(String tokenCookie, String ip, String type, String userEname) {
        String rtn = "";
        NameValuePair[] nameValuePairs = new NameValuePair[2];
        nameValuePairs[0] = new BasicNameValuePair("ParamType", "json");
        nameValuePairs[1] = new BasicNameValuePair("params",
                pariseXml(tokenCookie, ip, type, userEname));
        rtn = PostHttps.newInstance().getHttpsForStl(reqUrl, nameValuePairs, "POST");
        return rtn;
    }

    /**
     * description:  发送数据信息(socket)
     *
     * @param localIp
     * @return
     */
    private String socketSend(String tokenCookie, String localIp, String type, String userEname) {
        Socket sock = null;
        String rtnMsg = "";
        try {
            int port = Integer.parseInt(checkPort);
            sock = new Socket(checkIp, port);
            sock.setSoTimeout(60000);
            String strMsg = pariseXml(tokenCookie, type, localIp, userEname);
            log.info("send msg:[{}]", strMsg);
            byte[] sndMsg = strMsg.getBytes("GBK");
            sock.getOutputStream().write(sndMsg);
            sock.getOutputStream().flush();
            rtnMsg = receiveMsg(sock);
            log.info("receive msg:[{}]", strMsg);
            rtnMsg = rtnMsg.substring(4, rtnMsg.length());
            log.info("use receive msg:[{}]", rtnMsg);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            IOUtils.closeQuietly(sock);
        }
        return rtnMsg;
    }

    /**
     * description:  清空本地cookie
     *
     * @param request
     * @param response
     * @param path
     */
    @SuppressWarnings("rawtypes")
    private void clearCookie(HttpServletRequest request, HttpServletResponse response,
            String path) {
        Cookie[] cookies = request.getCookies();
        log.info("msg:[{}]", "开始执行清空cookie操作!");
        try {
            for (int i = 0; i < cookies.length; i++) {
                log.info("key=value:[{}],[{}]", cookies[i].getName(), cookies[i].getValue());
                Cookie cookie = new Cookie(cookies[i].getName(), null);
                cookie.setMaxAge(0);
                cookie.setPath(path);//根据你创建cookie的路径进行填写
                response.addCookie(cookie);
            }
        } catch (Exception ex) {
            log.info("msg:[{}]", "清空Cookies发生异常!");
            ex.printStackTrace();
        }
        if ("1".equals(isClearSession)) {
            ArrayList lst = new ArrayList();
            Enumeration enumer = request.getSession().getAttributeNames();
            while (enumer.hasMoreElements()) {
                lst.add(enumer.nextElement());
            }
            for (int i = 0; i < lst.size(); i++) {
                request.getSession().removeAttribute(lst.get(i).toString());
            }
            log.info("passport msg:[{}]", "clear session is over!");
        }
    }

    /**
     * description:  报文o
     *
     * @param strTokenId
     * @param localIp
     * @return
     */
    private String pariseXml(String strTokenId, String type, String localIp, String userEname) {
        String xml = "";
        Document doc = new Document();
        Element root = new Element("xml-body");
        Element head = new Element("Head");
        Element body = new Element("Body");
        //头 begin
        Element version = new Element("Version");
        version.setText("1.0");
        Element methodCode = new Element("MethodCode");
        methodCode.setText(type);
        Element beanId = new Element("BeanId");
        beanId.setText("ssoService");
        Element paramType = new Element("ParamType");
        paramType.setText("json");

        head.addContent(version);
        head.addContent(methodCode);
        head.addContent(beanId);
        head.addContent(paramType);
        //头 end

        //体 begin

        Element sysName = new Element("sysEname");
        sysName.setText(sysEname);

        Element sysAccreditip = new Element("sysAccreditip");
        sysAccreditip.setText(localIp);

        Element tokenId = new Element("tokenId");
        tokenId.setText(strTokenId);

        Element uEname = new Element("userEname");
        uEname.setText(userEname);

        body.addContent(sysName);
        body.addContent(uEname);
        body.addContent(sysAccreditip);
        body.addContent(tokenId);
        //体 end
        root.addContent(head);
        root.addContent(body);
        doc.addContent(root);
        XMLOutputter outputter = null;
        Format format = Format.getRawFormat();
        format.setEncoding("UTF-8");
        format.setIndent("    ");
        outputter = new XMLOutputter(format);
        xml = outputter.outputString(doc);
        return xml;
    }

    /**
     * description:  读取返回信息
     *
     * @param sock
     * @return
     */
    private String receiveMsg(Socket sock) {
        StringBuffer rtnMsgc = new StringBuffer();
        BufferedReader rd = null;
        String rtnMsg = "";
        try {
            rd = new BufferedReader(new InputStreamReader(sock.getInputStream(), "UTF-8"));
            String str;
            while ((str = rd.readLine()) != null) {
                rtnMsgc.append(str);
            }
            rtnMsg = rtnMsgc.toString();
        } catch (IOException e1) {
            e1.printStackTrace();
        } finally {
            IOUtils.closeQuietly(rd);
        }
        return rtnMsg;
    }

    public void init(FilterConfig arg0) throws ServletException {
        sysCode = arg0.getInitParameter("sysCode");
        //XmlWhiteUtils.getInstance().deWhiteXml(whiteMap, sysCode);//加载白名单
        initValue();
    }

    private void initValue() {
        //自定义登录页面
        login_url = ToolsUtil.getInstance().getValueByKey("uct_server");
        //cookieName 名称
        cookieName = ToolsUtil.getInstance().getValueByKey("cookieName");
        //获取系统名称
        sysEname = ToolsUtil.getInstance().getValueByKey("sysEname");
        //是否使用统一登录系统登录 T/F
        login_pass = ToolsUtil.getInstance().getValueByKey("login.pass");
        reqUrl = ToolsUtil.getInstance().getValueByKey("req_url");
        reqType = ToolsUtil.getInstance().getValueByKey("req_type");
        checkIp = ToolsUtil.getInstance().getValueByKey("check_url");
        checkPort = ToolsUtil.getInstance().getValueByKey("check_port");
        isClearSession = ToolsUtil.getInstance().getValueByKey("is_clear_session");
    }

    /**
     * description:  get校验
     *
     * @return
     */
    public String checkTokenGet(String tokenCookie, String ip, String type, String userEname) {
        GetMethod getMethod = null;
        String rtn = "";
        try {
            getMethod = new GetMethod(reqUrl);
            getMethod.setQueryString(new org.apache.commons.httpclient.NameValuePair[] {
                    new org.apache.commons.httpclient.NameValuePair("ParamType", "json"),
                    new org.apache.commons.httpclient.NameValuePair("params",
                            pariseXml(tokenCookie, type, ip, userEname)) });
            HttpClient httpClient = new HttpClient();
            int status = httpClient.executeMethod(getMethod);
            if (HttpStatus.SC_OK == status) {
                rtn = InputStreamToString(getMethod.getResponseBodyAsStream());
            } else {
                log.info("msg:[{}],[{}]", "请求失败!", status);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != getMethod)
                getMethod.releaseConnection();
        }
        return rtn;
    }

    /**
     * description:  将数据流转换成成string
     *
     * @param is
     * @return
     */
    private static String InputStreamToString(InputStream is) throws IOException {
        BufferedReader reader = null;
        StringBuffer responseText = new StringBuffer();
        String readerText = null;
        try {
            reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));
            readerText = reader.readLine();
            while (readerText != null) {
                responseText.append(readerText);
                responseText.append(System.getProperty("line.separator"));
                readerText = reader.readLine();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            IOUtils.closeQuietly(reader);
        }
        return responseText.toString();
    }

}
